package main.java.com.negromotad.genetics;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import main.java.com.negromotad.configuration.TMGeneticAlgorithmConfigurator;
import main.java.com.negromotad.genetics.rules.TMCompositeSelector;
import main.java.com.negromotad.genetics.rules.TMJepRule;
import main.java.com.negromotad.genetics.rules.TMRule;
import main.java.com.negromotad.genetics.rules.TMRuleSelector;
import main.java.com.negromotad.genetics.rules.TMTargetSelector;
import main.java.com.negromotad.model.TMAlgorithmObserver;
import main.java.com.negromotad.parser.TMDataLoader;

import org.jgap.Chromosome;
import org.jgap.Configuration;
import org.jgap.FitnessFunction;
import org.jgap.Gene;
import org.jgap.Genotype;
import org.jgap.IChromosome;
import org.jgap.InvalidConfigurationException;
import org.jgap.NaturalSelector;
import org.jgap.impl.DefaultConfiguration;
import org.jgap.impl.IntegerGene;

public class TMGeneticAlgorithm {

	private TMDataLoader loader;
	private TMGeneticAlgorithmConfigurator configurator;
	private TMAlgorithmObserver observer;

	public TMGeneticAlgorithm(TMDataLoader theLoader,
			TMGeneticAlgorithmConfigurator theConfigurator) {
		this.loader = theLoader;
		this.configurator = theConfigurator;
	}

	public TMChromosome calculateBestDistribution(List<TMRule> rules,
			int target, int expectative) throws InvalidConfigurationException {

		Configuration conf = new DefaultConfiguration();
		// Remove all post selectors
		conf.removeNaturalSelectors(false);
		conf.setPreservFittestIndividual(true);

		FitnessFunction myFunc = null;
		if (configurator.isMinimize()) {
			myFunc = new TMMinimizeFitnessFunction(loader.getEntry(), loader
					.getRows(), loader.getCols());
		} else {
			myFunc = new TMFitnessFunction(loader.getEntry(), loader.getRows(),
					loader.getCols());
		}

		conf.setFitnessFunction(myFunc);

		List<String> chromosome = new ArrayList<String>();
		for (String var : loader.getVars()) {
			chromosome.add(var);
		}

		TMTargetSelector targetSelector = new TMTargetSelector(loader
				.getEntry(), loader.getRows(), loader.getCols(), target,
				expectative);
		TMRuleSelector ruleSelector = new TMRuleSelector(chromosome, rules);
		List<NaturalSelector> selectors = new ArrayList<NaturalSelector>();
		selectors.add(ruleSelector);
		selectors.add(targetSelector);
		TMCompositeSelector compositeSelector = new TMCompositeSelector(
				selectors);
		conf.addNaturalSelector(compositeSelector, false);

		TMSmartOperator operator = new TMSmartOperator();
		conf.addGeneticOperator(operator);

		Chromosome sampleChromosome = new TMChromosome(conf, chromosome);

		conf.setSampleChromosome(sampleChromosome);
		conf.setPopulationSize(configurator.getPopulationSize());

		// Initialize population randomly
		Genotype population = Genotype.randomInitialGenotype(conf);

		// auxCode(entry, rows, cols, expectative, conf, chromosome);

		int failedEvolutions = 0;
		int sameResultEvolutions = 0;
		boolean goOn = true;
		boolean maxFailure = false;
		TMChromosome runFittestChromosome = null;
		TMChromosome evolutionFittestChromosome = null;
		for (int i = 0; i < configurator.getMaxAllowedEvolution() && goOn; i++) {
			//System.out.println("Evolution number: " + i);
			population.evolve();
//			show(new TMChromosome(conf, chromosome, population
//					.getFittestChromosome()), loader.getCols(), loader
//					.getEntry(), loader.getRows(), expectative);
			evolutionFittestChromosome = new TMChromosome(conf, chromosome,
					population.getFittestChromosome());

			if (runFittestChromosome != null
					&& runFittestChromosome.equals(evolutionFittestChromosome)) {
				sameResultEvolutions++;
			} else {
				sameResultEvolutions = 0;
				runFittestChromosome = evolutionFittestChromosome;
			}

			if (!validateChromosome(rules, runFittestChromosome,
					conf, target, loader, expectative)) {
				failedEvolutions++;
			} else
				failedEvolutions = 0;
			if (configurator.getMaxFailureEvolutions() < failedEvolutions) {
				System.out
						.println("Max failure evolutions reached. Stopping algorithm.");
				goOn = false;
				maxFailure = true;
				if (observer != null)
					observer.update(runFittestChromosome, i,goOn, maxFailure);
				return null;
			}
			if (configurator.getSameResultEvolutions() == sameResultEvolutions) {
				System.out
						.println(sameResultEvolutions
								+ " evolutions reached with same result. Stopping algorithm.");
				goOn = false;
			}
			if (observer != null)
				observer.update(runFittestChromosome, i,goOn, maxFailure);
		}
		// Get the fittest chromosome
		return new TMChromosome(conf, chromosome, population
				.getFittestChromosome());
	}

	private boolean validateChromosome(List<TMRule> rules,
			TMChromosome fittestChromosome,
			Configuration conf, int target, TMDataLoader loader, int expectative)
			throws InvalidConfigurationException {
		boolean evaluation = true;
		for (TMRule rule : rules) {
			evaluation = rule.evaluate(fittestChromosome.getChromosome());
			if (!evaluation)
				return evaluation;
		}
		if (fittestChromosome.getRisk(loader.getEntry(), expectative, loader
				.getRows(), loader.getCols()) > target) {
			return false;
		}
		return true;
	}

	/**
	 * Shows best chromosome for each evolution
	 * 
	 * @param bestSolutionSoFar
	 * @param cols
	 * @param entry
	 * @param rows
	 * @param expectative
	 */
	private static void show(TMChromosome bestSolutionSoFar, int cols,
			Double[][] entry, int rows, int expectative) {
		System.out.print("The best chromosome is: ");
		for (int i = 0; i < cols; i++) {
			System.out.print(bestSolutionSoFar.getGene(i).getAllele() + ", ");
		}
		System.out.println(" with a fitness value of: "
				+ bestSolutionSoFar.getFitnessValue()
				+ " and a total risk of: "
				+ bestSolutionSoFar.getRisk(entry, expectative, rows, cols));
	}

	public void setObserver(TMAlgorithmObserver observer) {
		this.observer = observer;
	}

}
